#: 26702 S2/Beginner's Corner 21-Feb-95 14:50:28 Thread=J20 Sb: #26247-OO vb Fm: Kathleen Joeris 75330,156 To: Pete Washburn 73750,3141 Pete, Sorry I did not reply sooner; I was busy over the weekend. (For anyone who wants to look at Pete's article: it is in the Beginner's Corner (sect 2) as VBOOP.ZIP.) Pete: I do not understand one aspect of what you are doing. This has to do with creating a new object of an inherited class. First question is what hOBJ is passed when the NEW_OBJECT is called? Second question, when does the pipeInstance and WaterpartInstance arrays get redimensioned? Third question, what is the UDT for the pipe class? Fourth question, is the following description of what happens when a new pipe is created correct? First, the pipeclass is called with a NEW_OBJECT message. Since this message is not handled by the pipeclass, it is passed to the waterpartclass with the NEW_OBJECT message. I expected this to create a new element in the WaterpartInstances array but I did not see a REDIM PRESERVE statement. Did I miss something here? You then use the self function to call pipeclass, changing the message to INIT_OBJECT INIT_OBJECT is not handled by the pipeclass, so this too is passed to the waterpartclass You then use the self function to call pipeclass, changing the message to SET_DIAMETER The pipeclass calls the WaterPartClass with the message SET_DIAMETER. This is the deepest point of the stack, and the stack is WaterPart (message = SET_DIAMETER) Pipe (message = SET_DIAMETER) Self (message = SET_DIAMETER) WaterPart (message = INIT_OBJECT) Pipe (message = INIT_OBJECT) Self (message = INIT_OBJECT) WaterPart (message = NEW_OBJECT) Pipe (message = NEW_OBJECT) The return value of Pipe(...SET_DIAMETER...) is an empty variant. This value is passed back, eventually to the calling routine. I took a good bit of time with this. I was unable to convince myself that it would work as stated. Perhaps you can explain where I missed the boat. Have you actually used this code, or was it over simplified when you wrote this? This seems to fully implement polymorphism, which in my mind is more important than inheritance. I really like this aspect of your design. You and Deborah hide the data within the function/module. I can see the benefits of doing this, but do not do it that way. Ultimately the speed of the seek is critical to this being practical. As I understand this design, you scan arrays eight times to perform a NEW_OBJECT. This shows why I do not do it this way. I believe that your inheritance would work. I think I either misunderstood some things, or it needs some fixes to this code, but the underlying theory seems entirely sound. I understand why you go through the inheritance stack three times, but it is still a bit unwieldly. This would particularly be a problem for a deeper inheritance stack. I am very curious as to how you handle the inheritance of DATA. You do not appear to do it in either of the manners I would have tried. One would have been Type PipeClass Parent as WaterPartClass Length as Double EndType and the other would have been to disallow any access to waterpart data of the pipe object except through messages sent to the waterpart class. THere are, as you mention, a number of things that could be done to make this more efficient. However, I think the basic theory is there in what you are doing. Have a good day, Kathleen ======================================================================= #: 375932 S0/Outbox File 22-Feb-95 17:32:00 Sb: OO vb Fm: VBPJFO REP 26702 To: Kathleen Joeris 75330,156 Hi Kathleen! Boy, you sure dug in! Good questions all. Let me see if I can clarify them somewhat. >First question is what hOBJ is passed when the NEW_OBJECT is called? I'm passing a Null when I create a new object as in: manifold1 = Pipe(Null, NEW_OBJECT, "2.5 100") The first thing NEW_OBJECT does is checks the hObj passed to it. If it is Null, then a new object is created (actually the object's handle). The hObj is created by ObjectDirectory and returned from the function. You then have a global handle to the object, in this example, manifold1 >Second question, when does the pipeInstance and WaterpartInstance >arrays get redimensioned? I'm showing my age and experience with QB 2.0, et. al. The days before Redim Preserve! Could very well use ReDim Preserve. By the way, how fast is it? Of course, one of my other answers will deal with performance issues and I'm sure it won't be significant as new objects aren't created all that often. In this example, the arrays are dimensioned during the INIT_CLASS method, as in: a = Pipe(Null, INIT_CLASS, 5) In this case, I'm not planning on more than 5 instances of this class, so the instance arrays are dimmed to 5 elements. No reason that you couldn't get rid of the INIT_CLASS method however and just Redim Preserve as necessary. Much more dynamic that way too!! >Third question, what is the UDT for the pipe class? I'm sorry, I can't recall what "UDT" is. >Fourth question, is the following description of what happens when a new >pipe is created correct? I don't have the actual file handy, but here's what I've got for NEW_OBJECT for the pipe class in my code here: Case NEW_OBJECT ' get a new object handle if it hasn't already been created If IsNull(hObj) Then hObj = ObjectDirectory(Null, NEW_OBJECT, PIPE_CLASS) End If ' create a new object instance in this class pipeCount = pipeCount + 1 hInst = pipeCount objectInstanceHandles(hObj) = hInst ' save the object handle in the instance pipeInstances(hInst).hObj = hObj ' create the ancestor instances a = WaterPart(hObj, NEW_OBJECT, value) ' initialize the object a = Pipe(hObj, INIT_OBJECT, parse(value)) ' return the object's handle Pipe = hObj Here's what's going on. If an object handle hasn't been created by a descendant class, then the first thing done is to create one. Next, the instance data is created. To speed access to the instance data later, the instance's data array index is stored in another array indexed by the object's handle. Then, the Pipe class passes the message on to the ancestor class, WaterPart in this case, so the WaterPart instances can be created for this object. Finally, the object is initialized with INIT_OBJECT. > First, the pipeclass is called with a NEW_OBJECT message. Yes > Since this message is not handled by the pipeclass, it is passed to the > waterpartclass with the NEW_OBJECT message. Yes it is handled by pipeClass and then passed on. Sorry if the file you downloaded didn't have that. > I expected this to create a new element in the WaterPartInstances array > but I did not see a REDIM PRESERVE statement. Did I miss something here? As discussed above, the PipeInstances array was dimmed earlier during INIT_CLASS in this example. Could ReDim Preserve. > You then use the self function to call pipeclass, changing the message to > INIT_OBJECT Yes. Now that all of the instance variables in the ancestor classes have been created, its safe to do all of the intializing that needs to be done. Here's the code for Pipe INIT_OBJECT: Case INIT_OBJECT Pipe = Self(hObj, SET_LENGTH, value) For this class, the only thing that has to be done is set the lenght of the pipe. Self is used just in case a descendant class has another way of processing SET_LENGTH. Note that when the object's handle hObj was created, one of the parameters to that call was the class of the object, the PIPE_CLASS in this case. When the object is created, the object's class is saved so that Self will know where to call to process any methods. So if this object was a descendant of Pipe and had another SET_LENGTH method, it would be called instead of the Pipe class message. If none of the descendant classes process the method, it will eventually get passed back here to the Pipe class. > INIT_OBJECT is not handled by the pipeclass, so this too is passed to the > waterpartclass Yes it is. See above. (Sorry if this is different than in the file) Note that each class's NEW_OBJECT calls INIT_OBJECT. This allows the instance variables for that class to be initialized. So the WaterPart INIT_OBJECT is processed before control is returned back to Pipe NEW_OBJECT (Continued) S2 #: 375938 S0/Outbox File 22-Feb-95 17:38:00 Sb: OO vb Fm: VBPJFO REP 26702 To: Kathleen Joeris 75330,156 (continued) > You then use the self function to call pipeclass, changing the message to > SET_DIAMETER > The pipeclass calls the WaterPartClass with the message SET_DIAMETER. > This is the deepest point of the stack, and the stack is > WaterPart (message = SET_DIAMETER) > Pipe (message = SET_DIAMETER) > Self (message = SET_DIAMETER) > WaterPart (message = INIT_OBJECT) > Pipe (message = INIT_OBJECT) > Self (message = INIT_OBJECT) > WaterPart (message = NEW_OBJECT) > Pipe (message = NEW_OBJECT) I suspect my code has updated that somewhat. Now, SET_DIAMETER is handled during WaterPart's INIT_OBJECT which is called from WaterPart's NEW_OBJECT. So the stack would be: WaterPart (message = SET_DIAMETER) Pipe (message = SET_DIAMETER) Self (message = SET_DIAMETER) WaterPart (message = INIT_OBJECT) WaterPart (message = NEW_OBJECT) Pipe (message = NEW_OBJECT) > The return value of Pipe(...SET_DIAMETER...) is an empty variant. This > value is passed back, eventually to the calling routine. In my working code, hObj get's passed back so you have a handle to the newly created object. The Pipe function (class) does pass back a variant type so that I have the flexiblity to return basically whatever is needed back. Before the variant type existed in VB, I returned everything as a string which could be then converted into whatever data type was needed. So even though I didn't catch on to the ReDim Preserve, I did see Variant as an enhancement to this OO thinking! > I took a good bit of time with this. I was unable to convince myself that > it would work as stated. Perhaps you can explain where I missed the boat. > Have you actually used this code, or was it over simplified when you wrote > this? The file consist of some messages I made several months ago (almost a year) During that time, the design has been enhanced and improved and hopefully with what I've added here, will make more sense. There wasn't much interest in the thread back then, so I hadn't spent a lot of time keeping it current. The code I've shown today is actual code that seems to be doing quite well, although I'm always trying to improve things (who isn't!) > This seems to fully implement polymorphism, which in my mind is more > important than inheritance. I really like this aspect of your design. > You and Deborah hide the data within the function/module. I can see the > benefits of doing this, but do not do it that way. Ultimately the speed > of the seek is critical to this being practical. As I understand this > design, you scan arrays eight times to perform a NEW_OBJECT. This shows > why I do not do it this way. > I believe that your inheritance would work. I think I either > misunderstood some things, or it needs some fixes to this code, but the > underlying theory seems entirely sound. I understand why you go through > the inheritance stack three times, but it is still a bit unwieldly. > This would particularly be a problem for a deeper inheritance stack. I think this nearly completely encompasses the three major features of OO design. Encapsulation, inheritance, and polymorphism. VB certainly isn't optimized for this type of design, but you can meet most of the principles this way and receive 95% of the benefits of OO design. I think the performance issues of any PURE OO design will always sacrifice speed for those benefits. It certainly was true with Actor (a Smalltalk like language). There sure is alot of message passing going on! I just don't see many ways to streamline the process without violating or losing some of the OO benefits. If you don't hardcode in the object's class, you need to make all those calls to Self to allow any descendant class it's chance to process the method. And if you don't pass any unhandled methods up to an ancestor, you're not gaining the inheritance benefits. If you start opening up the instance data globally, then you've lost the benefits of encapsulation. As always, life's a tradeoff. In my applications, 99.9% of the time the computer is waiting for the user to click on something, so the small performance penalty is insignificant compared with the easy of designing and maintaining the program. If this was a real time control program, then that equation would obviously change. Although I've not played with it, I think C++ is a reasonable tradeoff between OO principles and performance issues. You can take those shortcuts when necessary for performance with the full understanding for what you're giving up. With these techniques, I can do pretty much the same thing with VB. That wasn't an option with Actor; you always had do to things in a pure OO way. > I am very curious as to how you handle the inheritance of DATA. You do > not appear to do it in either of the manners I would have tried. One > would have been > Type PipeClass > Parent as WaterPartClass > Length as Double > EndType > and the other would have been to disallow any access to waterpart data of > the pipe object except through messages sent to the waterpart class. This is one of the two major limitations of doing this with VB; there isn't an easy way to inherit the data from an ancestor class. You could do it by including an instance variable of the ancestor's class in the class's instance variables as in: Type pipeObject hObj As Integer waterPart as WaterPartObject length As Integer End Type But then you have the problem of keeping the contents of waterPart the same as the instance variables in WaterPart. Plus if you get more than a couple of levels deep, it quickly becomes unmanageable. So I had to accept almost pure encapsulation in each class, even from it's ancestors. A pain in theory, but so far, hasn't been too much of a problem. The other major limitation was the need to track the class of each object and indexing it's instance data in the various classes. This is something that is automatic in the other OO languages I've seen. > There are, as you mention, a number of things that could be done to make > this more efficient. However, I think the basic theory is there in what > you are doing. Thanks. It's always nice to be appreciated :) Actually, I'm hopeing to gain some suggestions about improving it somewhat. Your suggestion of ReDim Preserve fits into that category, thanks. Pete Washburn, W.W. Programming #: 381305 S0/Outbox File 26-Feb-95 11:05:00 Sb: OO vb Fm: VBPJFO REP 27047 To: Kathleen Joeris [SL-1] 75330,156 Hi Kathleen, UDT - User Defined Type. I knew it looked familiar; just couldn't quite get a handle on it! Here's the pipe object: ' pipe objects Type pipeObject hObj As Integer length As Integer ' in feet volume As Integer ' in cubic feet change As Integer ' true/false End Type Pete Washburn, W.W. Programming